\chapter{定义函数}
在本章中我们将介绍一些在Haskell中定义函数的机制。我们首先介绍条件表达式和守卫等式，然后介绍一种简单却强大的模式匹配思想，最后
介绍lambda表达式和段的概念。

\section{以旧造新}
也许定义新函数最直接的方法就是简单地将已有的一个或多个函数结合起来。例如，下面展示的一些库函数就是用这种方法定义的：

\begin{itemize}
\item 判断一个字符是否是数字

\hspace*{1cm} $isDigit~::~Char \rightarrow Bool$\\
\hspace*{1cm} $isDigit~c~=~c \geq '0'~~\&\&~~c \leq '9'$

\item 判断一个整数是否是偶数

\hspace*{1cm} $even~::~Integral~a \Rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $even~c~=~n~`mod`~2 == 0$

\item 将一个列表在第$n$th个元素处拆分

\hspace*{1cm} $splitAt~::~Int \rightarrow [a] \rightarrow ([a],~[a])$\\
\hspace*{1cm} $splitAt~n~xs~=~(take~n~xs,~drop~n~xs)$

\item 倒数

\hspace*{1cm} $recip~::~Fractional~a \Rightarrow a \rightarrow a$\\
\hspace*{1cm} $recip~n~=~1~/~n$

\end{itemize}

注意上面$even$和$recip$类型中类约束的使用，精确的指明了这两个函数可以分别应用于任何整数类型和分数类型。

\section{条件表达式}
有一类函数，它们从许多种可能的结果中选择出一个最终结果，Haskell提供了很多不同的方式来定义这类函数。
最简单的方式就是使用\textit{条件表达式}，
条件表达式使用被称为\textit{条件}的逻辑表达式在两个相同类型的结果中选出一个。如果条件为\textit{真}，就选中第一个结果，否则选中第二个。例如：库函数$abs$的定义如下，该函数返回一个整数的绝对值：

\noindent\hspace*{1cm}$abs~::~Int \rightarrow Int$\\
\hspace*{1cm}$abs~n~=~\textbf{if}~n~ \geq ~0~ \textbf{then}~n~ \textbf{else} -n $

条件表达式可以嵌套，它们可以包含其他条件表达式。例如，库函数$signum$定义如下，它用来返回一个整型数的符号：

\noindent\hspace*{1cm}$signum~::~Int \rightarrow Int$\\
\hspace*{1cm}$signum~n~=~\textbf{if}~n~<~0~\textbf{then}~-1~\textbf{else} $\\
\hspace*{4cm}$\textbf{if}~n~==~\textbf{then}~0~\textbf{else}~1$

注意，与某些编程语言中的条件表达式不同，Haskell中的条件表达式必须包含\textbf{else}分支，这样就避免了众所周知的“else悬挂”问题。例如，如果\textbf{else}分支是可选的，那么表达式$\textbf{if}~True~\textbf{then~if}~False~\textbf{then}~1~\textbf{else}~2$既可以返回结果2，也可能产生一个错误，这取决于表达式中的\textbf{else}分支是被看作是内部条件表达式的一部分还是被看作是外部条件表达式的一部分。

\section{守卫等式}
作为条件表达式的一种替代方案，函数还可以使用\textit{守卫等式}来定义。在这类函数中，我们使用一些被称为\textit{守卫}的逻辑表达式从一些类型相同的结果中选择函数的最终结果。如果第一个守卫等式为\textit{真}，那么第一个结果被选中；否则如果第二个守卫等式为\textit{真}，则第二个结果被选中，依此类推。例如，库函数$abs$也可以以下面的方式定义： 

\begin{tabular}[t]{lll}
$abs~n~$&$|~n\geq 0$&$= ~ n$\\
&$|~otherwise$&$=~-n$\\
\end{tabular}

符号|读作“满足于，使得”。守卫$otherwise$在标准Prelude库文件中简单地定义为$otherwise~=~True$。虽然以$otherwise$作为一系列守卫的结尾不是必要的，但这样做为处理“所有其他情况”提供了一种便利的方式，同时也清楚地避免了因所有守卫都不为真而出错的情况。

较之条件表达式，使用守卫等式定义函数可读性更好。例如：使用如下守卫等式定义的库函数$signum$更容易理解。

\begin{tabular}[t]{lll}
$signum~n$&$|~n~<~0$&$=~-1$\\
&$|~n~==~0$&$=~0$\\
&$|~otherwise$&$=~1$\\
\end{tabular}

\section{模式匹配}

使用\textit{模式匹配}可以使许多函数拥有一个极为简单且直观的定义。在这类函数定义中，我们使用一些被称为\textit{模式}的语法表达式从一些类型相同的结果中选择出函数的最终结果。如果第一个模式匹配成功，那么第一个结果被选中；否则，如果第二个模式匹配成功，那么第二个结果被选中，依此类推。例如，库函数$not$的定义如下，它用来返回一个对逻辑值取非的结果：

\begin{tabular}[t]{lll}
$not$&::&$Bool \rightarrow Bool$\\
$not~False$&=&$True$\\
$not~True$&=&$False$\\
\end{tabular}

接受多个参数的函数也可以使用模式匹配定义，这种情况下，每个等式中各个参数的模式按顺序进行匹配。例如，库操作符$\&\&$定义如下，该操作符返回两个逻辑值与后的结果：

\begin{tabular}[t]{lll}
$(\&\&)$&::&$Bool \rightarrow Bool \rightarrow Bool$\\
$True~\&\&~True$&=&$True$\\
$True~\&\&~False$&=&$False$\\
$False~\&\&~True$&=&$False$\\
$False~\&\&~False$&=&$False$\\
\end{tabular}

然而，我们可以通过将最后三个等式合并为一个等式来简化这个函数的定义，合并后的等式使用可匹配任何值的\textit{通配符}模式$\_$，并返回与两个参数值无关的结果$False$。

\begin{tabular}[t]{lll}
$True~\&\&~True$&=&$True$\\
$\_~\&\&~\_$&=&$False$\\
\end{tabular}

根据第12章讨论的惰性求值，这个版本的函数定义还有这样的好处：如果第一个参数为$False$，那么我们可直接返回结果$False$,而无须对第二个参数进行求值。在实际中，标准库prelude使用了同样具有这个属性的等式定义$\&\&$。但只使用了第一个参数的值来选择哪个等式作为最终结果：

\begin{tabular}[t]{lll}
$True~\&\&~b$&=&$b$\\
$False~\&\&~\_$&=&$False$\\
\end{tabular}

即如果第一个参数为$True$，那么结果为第二个参数的值；如果第一个参数为$False$，那么结果就是$False$。

注意，因技术原因在一个等式中同样的名字不能被用于多个参数。例如，下面的操作符$\&\&$的定义就是基于这个观察：如果两个参数相等，那么结果也是同样的值，否则结果为$False$。但是由于上面的命名要求，这个定义是无效的：

\begin{tabular}[t]{lll}
$b~\&\&~b$&=&$b$\\
$\_~\&\&~\_$&=&$False$\\
\end{tabular}

然而如果需要，我们可以使用守卫等式来定义一个有效的版本，守卫等式用来判断两个参数是否相等：

\begin{tabular}[t]{lll}
$b~\&\&~c$ & $|~b~==~c$ &= $b$\\
& $|otherwise$ &= $False$\\
\end{tabular}

到目前为止，我们只考虑了基本模式，要么是值、要么是变量或是通配符模式。在本节其余部分，我们将介绍三种有用的将较小模式结合成较大模式的方法。

\subsection*{元组模式}
一个模式元组的本身就是一个模式，它可以匹配任何元数相同且所有元素都可按顺序匹配对应模式的元组。例如，库函数$fst$和$snd$定义如下，它们分别返回二元组的第一个和第二个元素：

\begin{tabular}[t]{lll}
$fst$&::&$(a,~b) \rightarrow a$\\
$fst~(x,~\_)$&=&$x$\\
\end{tabular}

\begin{tabular}[t]{lll}
$snd$&::&$(a,~b) \rightarrow b$\\
$snd~(\_,~y)$&=&$y$\\
\end{tabular}

\subsection*{列表模式}
同样，一个模式列表本身是一个模式，它可以匹配任何长度相同且所有元素都可按顺序匹配对应模式的列表。例如，用来判断一个列表是否精确的包含三个元素且第一个元素为'$a$'的函数$test$定义如下：

\begin{tabular}[t]{lll}
$test$&::&$[Char] \rightarrow Bool$\\
$test~['a',~\_,~\_]$&=&$True$\\
$test~\_$&=&$False$\\
\end{tabular}

到目前为止，我们一直将列表视为Haskell内置的原子类型。但事实上并非如此，它们实际上是使用操作符$:$从空列表[~]开始一次一个元素的构造起来的。操作符$:$被称为$cons$，它通过将一个新元素加到一个已存在列表的开始处来构造一个新的列表。例如，列表$[1,~2,~3]$可以按如下分解：

\noindent\hspace*{1cm} $[1,~2,~3]$\\
\hspace*{1cm} = \{列表记法\}\\
\hspace*{1cm} $1~:~[2,~3]$\\
\hspace*{1cm} = \{列表记法\}\\
\hspace*{1cm} $1~:~(2:~[3])$\\
\hspace*{1cm} = \{列表记法\}\\
\hspace*{1cm} $1~:~(2:~(3:~[]))$

即$[1,~2,~3]$只是$1~:~(2~:~(3~:~[~]))$的一个缩写。为了避免在使用这样的列表时过度使用括号，cons操作符被假定为是右结合的。例如，$1:~2:~3:~[~]$意为$1:~(2:~
(3:~ [~]))$。

cons操作符不仅可以用来构造列表，也可以用来构造模式，用于匹配任何非空且第一个元素及其余元素都可按顺序匹配对应模式的列表。例如，我们现在可以定义一个更通用的函数$test$的版本，用于判断包含任意数量字符的列表是否以'$a$'开头： 

\begin{tabular}[t]{lll}
$test$&::&$[Char] \rightarrow Bool$\\
$test~('a'~:~\_)$&=&$True$\\
$test~\_$&=&$False$\\
\end{tabular}

同样，库函数$null$，$head$和$tail$的定义如下，它们分别用于判断一个列表是否为空，从一个非空列表中选出第一个元素以及从一个非空列表中删除第一个元素：

\begin{tabular}[t]{lll}
$null$&::&$[a] \rightarrow Bool$\\
$null~[~]$&=&$True$\\
$null~(\_:\_)$&=&$False$\\
\end{tabular}

\begin{tabular}[t]{lll}
$head$&::&$[a] \rightarrow a$\\
$head~(x:\_)$&=&$x$\\
\end{tabular}

\begin{tabular}[t]{lll}
$tail$&::&$[a] \rightarrow a$\\
$tail~(\_:~xs)$&=&$xs$\\
\end{tabular}

注意cons操作符必须用括号括上，因为函数优先级高于所有其他操作符。例如，不带括号的定义$tail~\_:~xs~=~xs$意为$(tail~\_)~:~xs~=~xs$，它不仅含义不正确，而且还是一个无效的定义。

\subsection*{整数模式}
作为一种有时很有用的特殊情况，Haskell也允许$n + k$形式的整数模式\footnote{译注：$n+k$模式在Haskell 2010标准中默认被禁用。若使用$n+k$模式，在GHC 7及后续版本中必须开启NPlusKPatterns扩展。}，其中$n$是一个整数变量，$k > 0$是一个整数常量。例如，函数$pred$定义如下，它将0映射到本身且将任何正整数映射为该整数的前驱：

\begin{tabular}[t]{lll}
$pred$&::&$Int \rightarrow Int$\\
$pred~0$&=&$0$\\
$pred~(n~+~1)$&=&$n$\\
\end{tabular}

关于$n~+~k$模式有两点需要注意。首先，它们只是匹配$\geq ~k$的整数。例如，对$pred~(-1)$求值将产生一个错误，因为$pred$定义中的两个模式都不匹配负数。其次，和cons模式一样，整数模式必须用括号括起来。例如，不带括号的定义$pred~n~+~1~=~n$意为$(pred~n)~+~1~=~n$，这是一个无效的定义。

\section{Lambda表达式}
作为使用等式定义函数的一种替代方案，函数还可以使用\textit{lambda表达式}来构造。lambda表达式包含一个用于参数匹配的模式以及一个详细说明了如何根据参数计算出结果的主体。lambda表达式定义的函数没有函数名，换句话说，lambda表达式是匿名函数。

例如，一个只接受一个数字为参数且返回结果$x~+~x$的匿名函数可以按下面方式构造：

\noindent\hspace*{1cm} $\lambda x \rightarrow x~+~x$

符号$\lambda$是小写希腊字母“lambda”。尽管lambda表达式构造的函数没有名字，但它们仍然可以像其他函数那样使用。例如：

\noindent\hspace*{1cm} $>~(\lambda x \rightarrow x~+~x)~~2$\\
\hspace*{1cm} $4$

除了自身有趣之外，lambda表达式还有很多实际应用。首先，它们可以用来形式化curried函数定义的含义。例如，定义

\noindent\hspace*{1cm} $add~x~y~=~x~+~y$

可以被理解为

\noindent\hspace*{1cm} $add~=~\lambda x \rightarrow (\lambda y \rightarrow x~+~y)$

这里精确地表明$add$是一个函数，它接受一个数字$x$作为参数且返回一个函数，而这个返回的函数依次接受数字$y$为参数且返回结果$x~+~y$。

其次，当定义返回值的本质为函数而不是curring结果的函数时，lambda表达式也非常有用。例如，库函数$const$定义如下，它返回一个总是产生给定值的常量函数：

\begin{tabular}[t]{lll}
$const$&::&$a \rightarrow b \rightarrow a$\\
$const~x~\_$&=&$x$\\
\end{tabular}

但是，更吸引人地定义$const$的方式是通过在类型中使用括号，在定义中使用lambda表达式以显式地指出用函数作为返回结果：

\begin{tabular}[t]{lll}
$const$&::&$a \rightarrow (b \rightarrow a)$\\
$const~x~$&=&$\lambda \_ \rightarrow x$\\
\end{tabular}

最后，lambda表达式可用于避免给仅被引用一次的函数命名。例如，函数$odds$定义如下，它用于返回前$n$个奇数：

\begin{tabular}[t]{lll}
$odds$&::&$Int \rightarrow [Int]$\\
$odds~n$&=&$map~f~[0..n-1]$\\
& &\textbf{where}~$~f~x~=~x~*~2~+~1$\\
\end{tabular}

（库函数$map$用于将一个函数应用于一个列表中的所用元素。）但是，由于本地定义的函数$f$只被引用一次，$odds$的定义可以使用lambda表达式简化为：

\noindent\hspace*{1cm} $odds~n~=~map~(\lambda x \rightarrow x~*~2~+~1)~[0..n-1]$

\section{段}
像$+$这样的写在两个参数之间的函数我们称之为\textit{操作符}。正如我们已经看到的，通过将函数名用反单引号括起来， 任何接受两个参数的函数都可以被转换成一个操作符，就像在$7~`div`~2$中的那样。然而，反过来也是可能的。特别是通过将操作符用括号括起来并放在其参数的前面，任何一个操作符都可以被转换成一个curried函数，就像在$(+)~1~2$中的那样。此外，如果需要，这种转换也允许其中的一个参数被包含在括号中，就像在$(1+)~2$和$(+2)~1$中的那样。

通常，如果(⊕)是一个操作符，那么对于参数$x$和$y$，(⊕)、($x$ ⊕)和(⊕
$y$)形式的表达式被称为\textit{段}(sections)，作为函数其含义可以用lambda表达式形式化如下：

\begin{tabular}[t]{lll}
(⊕)&=&$\lambda x \rightarrow (\lambda y \rightarrow x$ ⊕ $y)$\\
($x$ ⊕)&=&$\lambda y \rightarrow x $ ⊕ $y$\\
(⊕ $y$)&=&$\lambda x \rightarrow x $ ⊕ $y$\\
\end{tabular}

段有三种主要应用。首先，它们可以用一种紧凑的方式来构造一些简单实用的函数，如下面的例子所示：

\begin{tabular}[t]{lll}
$(+)$&是加法函数 $\lambda x \rightarrow (\lambda y \rightarrow x~+~y)$\\
$(1+)$&是后继函数 $\lambda y \rightarrow 1~+~y$\\
$(1/)$&是倒数函数 $\lambda y \rightarrow 1~/~y$\\
$(*2)$&是倍数函数 $\lambda x \rightarrow x~*~2$\\
$(/2)$&是二等分函数 $\lambda x \rightarrow x~/~2$\\
\end{tabular}


其次，当描述操作符类型时段也是必要的，因为在Haskell中一个操作符本身并不是一个有效表达式。例如，逻辑与操作符$\&\&$的类型可以描述如下：

\noindent\hspace*{1cm} ($\&\&$)~::~$Bool \rightarrow Bool \rightarrow Bool$

最后，当操作符作为其他函数参数时，段也是必要的。例如用于判断一个列表中的所有逻辑值是否都为$True$的库函数$and$就是用操作符$\&\&$作为库函数$foldr$的参数而定义的，函数$foldr$将在第7章中讨论。

\begin{tabular}[t]{lll}
$and$&::&$[Bool] \rightarrow Bool$\\
$and$&=&$foldr~(\&\&)~True$\\
\end{tabular}

\section{本章备注}
使用更多语言原生特点翻译的模式匹配的正式含义在\textbf{Haskell
Report}(25)中给出。定义匿名函数时所使用的希腊字母$\lambda$来自$\lambda$\textit{算子}，Haskell就是以这套函数的数学理论为基础构建的。

\section{习题} 

\begin{enumerate}

\item 使用库函数，定义一个函数$halve~::~[a] \rightarrow([a], ~[a])$用于将一个长度为偶数的列表一分为二，例如：

\noindent\hspace*{1cm} $>~ halve~[1,~2,~3,~4,~5,~6]$\\
\hspace*{1cm} $([1,~2,~3],~[4,~5,~6])$

\item 考虑一个函数$safetail~::~[a] \rightarrow
[a]$，它的行为与库函数$tail$相似，除了$safetail$将空列表映射为其自己，而$tail$在这种情况下会产生一个错误，使用如下方式定义$safetail$： 
\begin{enumerate}
\item 使用一个条件表达式； 
\item 使用守卫等式； 
\item 使用模式匹配； 
\end{enumerate}

提示：使用库函数$null$。

\item 以相同的方式展示逻辑或$||$操作符使用模式匹配的四种定义方式。

\item 使用条件表达式而不是模式匹配重新定义下面版本的逻辑与操作符：

\begin{tabular}[t]{lll}
$True~\&\&~True$&=&$True$\\
$\_~\&\&~\_$&=&$False$\\
\end{tabular}

\item 对下面的版本做同样的事情，注意需要的条件表达式的数量的差别。

\begin{tabular}[t]{lll}
$True~\&\&~b$&=&$b$\\
$False~\&\&~\_$&=&$False$\\
\end{tabular}

\item 展示一下curried函数$mult~x~y~z~=~x~*~y~*~z$如何使用lambda表达式理解。

\end{enumerate}

